﻿/*
 * Toast8
 * All rights reserved
 * Copyright (C) 2014 Peter Nylander
 */

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.IO;
using System.Net;
using System.Net.Http.Headers;

namespace Toast8.SpotifyHelper
{
    /// <summary>
    /// Spotify Web Helper API client. Perform simple tasks against the SpotifyWebHelper process such as playing tracks etc. pausing, returning status etc.
    /// </summary>
    public class SpotifyWebHelperApiClient
    {
        private HttpClient httpClient;

        public SpotifyToken OAuthToken { get; private set; }
        public SpotifyToken CSRFToken { get; private set; }

        private readonly string GET_OAUTH_TOKEN_URL = "http://open.spotify.com/token";
        private readonly string GET_CSRF_TOKEN_URL = "/simplecsrf/token.json";

        private Dictionary<string, string> coverArtUrlCache = new Dictionary<string, string>();

        public bool Initialized { get; private set; }

        public SpotifyWebHelperApiClient()
        {
            // Set max number of entries in dictionary
            //coverArtUrlCache.

            Initialized = false;

            httpClient = new HttpClient();

            // Origin and Referer must be set to get around spotify's attempts to protect the service
            httpClient.DefaultRequestHeaders.Add("Origin", "https://open.spotify.com");
            httpClient.DefaultRequestHeaders.Add("Referer", "https://www.facebook.com");
        }

        public async Task Initialize()
        {
            if (IsAvailable())
            {
                try
                {
                    OAuthToken = await GetOAuthToken();
                    CSRFToken = await GetCSRFToken();
                    Initialized = true;
                }
                catch (Exception)
                {
                    Initialized = false;
                }
            }
            else
            {
                // Try to start web helper if it's not started
                StartSpotifyWebHelper();
            }
        }

        public bool IsAvailable()
        {
            Process[] p = Process.GetProcesses();

            Process webHelperProcess = Process.GetProcesses().FirstOrDefault(f => f.ProcessName == "spotify");
            return (webHelperProcess != null);
        }

        public void StartSpotifyWebHelper()
        {
            Process webHelperProcess = Process.GetProcesses().FirstOrDefault(f => f.ProcessName == "SpotifyWebHelper");
            if (webHelperProcess == null)
            {
                try
                {
                    // The SpotifyWebHelper is not running attempt to start it
                    Process.Start(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Spotify\\Data\\SpotifyWebHelper.exe");
                }
                catch (Exception e)
                {
                    throw new Exception("Failed to launch SpotifyWebHelper", e);
                }
            }
        }

        public async Task<SpotifyToken> GetOAuthToken()
        {
            try
            {
                HttpResponseMessage response = await httpClient.GetAsync(GET_OAUTH_TOKEN_URL, HttpCompletionOption.ResponseContentRead);

                string tokenResponse = await response.Content.ReadAsStringAsync();
                response.EnsureSuccessStatusCode();

                return JsonConvert.DeserializeObject<SpotifyToken>(tokenResponse);
            }
            catch (Exception)
            {
                throw;
            }
        }

        public async Task<SpotifyToken> GetCSRFToken()
        {
            HttpResponseMessage response = await httpClient.GetAsync(GetUrl() + GET_CSRF_TOKEN_URL, HttpCompletionOption.ResponseContentRead);

            string tokenResponse = await response.Content.ReadAsStringAsync();
            response.EnsureSuccessStatusCode();

            SpotifyToken token = JsonConvert.DeserializeObject<SpotifyToken>(tokenResponse);
            if (token != null && token.Error != null)
            {
                throw new Exception(token.Error.Type + " : " + token.Error.Message);
            }

            return token;
        }

        public string GetUrl()
        {
            return "http://localhost:4380";
        }

        public async Task<string> GetVersion()
        {
            string requestUrl = GetUrl() + "/service/version.json?service=remote";
            return await httpClient.GetStringAsync(requestUrl);
        }

        public async Task<Status> GetCurrentStatus()
        {
            string requestUrl = GetUrl() +
                string.Format("/remote/status.json?oauth={0}&csrf={1}&ref=", OAuthToken.OAuthToken, CSRFToken.Token);

            string response = await httpClient.GetStringAsync(requestUrl);

            Status status = JsonConvert.DeserializeObject<Status>(response);

            if (status.Error != null)
            {
                // An error occured
                Debug.WriteLine("Error: " + status.Error.Type + " : " + status.Error.Message);

                //4102: "Invalid OAuth token",
                //4103: 'EXPIRED_OAUTH_TOKEN',
                //4104: 'OAUTH_TOKEN_NOT_VERIFIED',
                //4105: 'TOKEN_VERIFICATION_DENIED_TOO_MANY_REQUESTS',
                //4106: 'TOKEN_VERIFICATION_TIMEOUT',
                //4107: 'INVALID_CSRF_TOKEN',
                //4108: 'INVALID_OAUTH_FOR_CURRENT_USER',
                //4109: 'INVALID_CSRF_PATH',
                //4110: 'NO_USER_LOGGED_IN',
                return status;
            }

            // Get thumbnail url
            // Cache thumbnail urls
            // Set max limit of dictionary size
            try
            {
                if (coverArtUrlCache.ContainsKey(status.Track.AlbumResource.Uri))
                {
                    status.Track.AlbumResource.ThumbnailUrl = coverArtUrlCache[status.Track.AlbumResource.Uri];
                }
                else
                {
                    CoverArt art = await GetCoverArt(status.Track.AlbumResource);
                    coverArtUrlCache.Add(status.Track.AlbumResource.Uri, art.ThumbnailUrl);

                    status.Track.AlbumResource.ThumbnailUrl = art.ThumbnailUrl;
                }
            }
            catch (Exception e)
            {
                Debug.WriteLine("Exception: " + e.Message + " " + e.ToString());
            }
            return status;
        }

        /// <summary>
        /// Get a link to the cover art image of a spotify album
        ///
        /// We will get an url like this. Change "cover" to sizes for image without badge
        /// Sizes 60, 85, 120, 300, and 640, image or thumb
        /// https://d3rt1990lpmkn.cloudfront.net/cover/3d09dc8b12a483e84e16b40bbb97c7ecf15e429a
        /// url: http://o.scdn.co/640/3d09dc8b12a483e84e16b40bbb97c7ecf15e429a
        /// </summary>
        /// <param name="uri">The Spotify album URI</param>
        /// <returns></returns>
        public async Task<CoverArt> GetCoverArt(Resource resource)
        {
            try
            {
                //url = "https://embed.spotify.com/oembed/?url=spotify:artist:7ae4vgLLhir2MCjyhgbGOQ";

                HttpClient client = new HttpClient();
                client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0");
                client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue() { MaxAge = TimeSpan.Zero };
                client.DefaultRequestHeaders.Connection.Add("keep-alive");
                client.DefaultRequestHeaders.Add("Accept", "application/json");

                string url = "https://embed.spotify.com/oembed/?url=" + resource.Uri;

                HttpResponseMessage httpResponse = await client.GetAsync(url, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false);

                string response = await httpResponse.Content.ReadAsStringAsync();

                return JsonConvert.DeserializeObject<CoverArt>(response);
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Plays a spotify uri
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public async Task<Status> Play(string uri)
        {
            string path = "remote/play.json?uri=" + uri;

            string requestUrl = GetUrl() +
                string.Format("/remote/play.json?uri={0}&oauth={1}&csrf={2}", uri, OAuthToken.OAuthToken, CSRFToken.Token);

            string response = await httpClient.GetStringAsync(requestUrl);

            return JsonConvert.DeserializeObject<Status>(response);
        }

        /// <summary>
        /// Pauses the current playback
        /// </summary>
        /// <returns></returns>
        public async Task<Status> Pause()
        {
            string requestUrl = GetUrl() +
                string.Format("/remote/pause.json?pause=true&oauth={0}&csrf={1}", OAuthToken.OAuthToken, CSRFToken.Token);

            string response = await httpClient.GetStringAsync(requestUrl);

            return JsonConvert.DeserializeObject<Status>(response);
        }

        /// <summary>
        /// Resumes the current playback
        /// </summary>
        public async Task<Status> Resume()
        {
            string requestUrl = GetUrl() +
                string.Format("/remote/pause.json?pause=false&oauth={0}&csrf={1}", OAuthToken.OAuthToken, CSRFToken.Token);

            string response = await httpClient.GetStringAsync(requestUrl);

            return JsonConvert.DeserializeObject<Status>(response);
        }
    }
}
